package classLoad;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

import model.Filter;

import util.GAConfig;
public class CustomClassLoader extends ClassLoader {
	   private Hashtable<String,Class<?>> classes = new Hashtable<String,Class<?>>();
	   private final Map<String, Constructor<?>> constructorCache = new HashMap<String, Constructor<?>>();

	private String cut;
    public CustomClassLoader(){
        super(CustomClassLoader.class.getClassLoader());
    }
    public Class<?> loadClass(String className) throws ClassNotFoundException {
    //	System.err.println("CustomClassLoader.loadClass: "+ className);
         return findClass(className);
    }
    public  void setCut(String cut){
    	this.cut=cut;
    }
    public Hashtable<String,Class<?>> getClasses(){
    	return  classes;
    }
    public Class<?> findClass(String className){
        byte classByte[];
        Class<?> result=null;
        result = (Class<?>)classes.get(className);
        if(result != null){
            return result;
        }
        try{
        	if(className.contains("instrumentation")||className.startsWith("java."))
        		return findSystemClass(className);
        }catch(Exception e){
       	 System.err.print("error in loading class: "+className+ "  "+ e.toString());
       	 return null;
        }

        try{
        	
        	String classPath = "/"+ ((String)ClassLoader.getSystemResource(className.replace('.',File.separatorChar)+".class").getFile()).substring(1);

        if(className.equals(this.cut)){
        	final String fileName = this.cut.replace('.', File.separatorChar) + ".class";
        	File element = GAConfig.getDirInstrumented();
        	File w = new File(element, fileName);
        	classPath =w.getAbsolutePath();//  element.getAbsolutePath()+File.separatorChar+"Cart.class" ;// ((String)ClassLoader.getSystemResource(className.replace('.',File.separatorChar)+".class").getFile()).substring(1);
        }
        classPath=classPath.replace("%5c", "/");
        
           classByte = loadClassData(classPath);
            result = defineClass(className,classByte,0,classByte.length,null);
            classes.put(className,result);
            return result;
        }catch(Exception e){
        } 

        try{
        	//if(className.startsWith("java"))
        		return findSystemClass(className);
        }catch(Exception e){
       	 System.err.print("error in loading class: "+className+ "  "+ e.toString());
       	 return null;
        }
        
    }
 
    private byte[] loadClassData(String className) throws IOException{
 
        File f ;
        f = new File(className);
        int size = (int)f.length();
        byte buff[] = new byte[size];
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        dis.readFully(buff);
        dis.close();
        return buff;
    }
 
 	public Constructor<?> getConstructor(Constructor<?> cns) throws ClassNotFoundException, SecurityException, NoSuchMethodException {
 		try{
		Constructor<?> cache = constructorCache.get(cns.toString());
		if(cache != null) return cache;

		Class<?> c = loadClass(cns.getDeclaringClass().getName());
		Class<?>[] params = getClasses(cns.getParameterTypes());
		Constructor<?> constructor = c.getDeclaredConstructor(params);
		constructorCache.put(cns.toString(), constructor);
		return constructor;
 		}catch(Exception e){
        	System.err.println("CustomClassLoader.getConstructor : "+cns.toString() +" "+  e.toString());
        	return null;
			
		}

	}
	private final Map<String, Method> methodCache = new HashMap<String, Method>();
	public Method getMethod(Method m) throws ClassNotFoundException, SecurityException, NoSuchMethodException {
		try{
		Method cache = methodCache.get(m.toString());
		if(cache != null) return cache;

		Class<?> c = findClass(m.getDeclaringClass().getName());
		Class<?>[] params = getClasses(m.getParameterTypes());
		Method method=c.getDeclaredMethod(m.getName(), params);
//		Method method = c.getMethod(m.getName(), params);
		methodCache.put(m.toString(), method);

		return method;
        }catch(Exception e){
        	System.err.println("CustomClassLoader.getMethod : "+m.toString() +" "+  e.toString());
        	return null;
        } 

	}

	public Class<?>[] getClasses(Class<?>[] c) throws ClassNotFoundException {

		Class<?>[] ret = new Class<?>[c.length];
		for (int i = 0; i < ret.length; i++)
			if(Filter.isPrimitiveOrStringType(c[i])||c[i].isArray())
				ret[i] = c[i];
			else
				ret[i] = findClass(c[i].getName());

		return ret;
	}
}
